#include "stdafx.h"
#include "serialise\json.h"
#include "serialise\database.h"
#include "serialise\serialisetypes.h"
#include "serialise\jsonserialise.h"

namespace Serialisation
{
	SERIALISATION_IS_ENUM(Json::Schema::Type)
	SERIALISATION_IS_ARRAY(Json::JArray)
}

namespace Json
{

using namespace Serialisation;

template <typename Archive>
void Serialise(Archive& ar, JBase& t)
{
}

template <typename Archive>
void Serialise(Archive& ar, JArray& t)
{
	for (int i = 0; i < (int) t.m_elements.size(); i++)
	{
		if (t.m_elements[i]->GetObject())
			ar.ArrayElement(*t.m_elements[i]->GetObject());
		if (t.m_elements[i]->GetArray())
			ar.ArrayElement(*t.m_elements[i]->GetArray());
		if (t.m_elements[i]->GetString())
			ar.ArrayElement(Unquoted(*t.m_elements[i]->GetString()));
	}
}

template <typename Archive>
void Serialise(Archive& ar, JObject& t)
{
	for (auto it = t.m_elements.begin(); it != t.m_elements.end(); ++it)
	{
		if (it->second->GetObject())
			ar.Serialise(it->first.c_str(), *it->second->GetObject());
		if (it->second->GetArray())
			ar.Serialise(it->first.c_str(), *it->second->GetArray());
		if (it->second->GetString())
			ar.Serialise(it->first.c_str(), Unquoted(*it->second->GetString()));
	}
}

template <typename Archive>
void Deserialise(Archive& ar, JObject& t)
{
	JObject* pObject = ar.GetObject();

	for (auto it = pObject->m_elements.begin(); it != pObject->m_elements.end(); ++it)
		t.Insert(it->first, it->second->DeepCopy());
}

namespace Schema
{

template <typename Archive>
void Serialise(Archive& ar, Type& t)
{
	SERIALISATION_ENUM_BEGIN(Type)
	SERIALISATION_ENUM_VALUE(Type::Null, Null)
	SERIALISATION_ENUM_VALUE(Type::Boolean, Boolean)
	SERIALISATION_ENUM_VALUE(Type::Int, Int)
	SERIALISATION_ENUM_VALUE(Type::Float, Float)
	SERIALISATION_ENUM_VALUE(Type::String, String)
	SERIALISATION_ENUM_VALUE(Type::Object, Object)
	SERIALISATION_ENUM_VALUE(Type::Enum, Enum)
	SERIALISATION_ENUM_VALUE(Type::Reference, Reference)
	SERIALISATION_ENUM_END(Type, t)
}

template <typename Archive>
void Serialise(Archive& ar, FieldInfo& t)
{
	ar.Serialise("Type", t.basicType);
	ar.Serialise("TypeName", t.typeName);
	ar.Serialise("IsArray", t.isArray);
}

template <typename Archive>
void Serialise(Archive& ar, Object& t)
{
	ar.Serialise("Fields", t.fields);
}

template <typename Archive>
void Serialise(Archive& ar, Enum& t)
{
	ar.Serialise("Values", t.values);
}

}

template <typename Archive>
void Serialise(Archive& ar, Record& t)
{
	ar.Serialise("Object", t.pObject);
	ar.Serialise("Schema", t.schema);
}

template <typename Archive>
void Serialise(Archive& ar, Folder& t)
{
	ar.Serialise("Folders", t.m_folders);
	ar.Serialise("Records", t.m_records);
}

template <typename Archive>
void Serialise(Archive& ar, Database& t)
{
	ar.Serialise("Name", t.m_name);
	ar.Serialise("Schemas", t.m_schemas);
	ar.Serialise("Enums", t.m_enums);
	ar.Serialise("Folder", static_cast<Folder&>(t));
}

void RegisterTypes()
{
	static bool done = false;

	if (!done)
	{
		SERIALISATION_REGISTER(Json::Serialiser, Database)
		SERIALISATION_REGISTER(Json::Serialiser, JObject)
		SERIALISATION_REGISTER(Json::Serialiser, Schema::Object)
	}

	done = true;
}

void ReadValue(const std::string& input, std::string& t)
{
	// to do: handle escape characters
	std::string::size_type end = input.find_first_of('\"', 1);

	if (end == std::string::npos)
		t = "";
	else
		t = input.substr(1, end - 1);
}

void ReadValue(const std::string& input, bool& t)
{
	std::stringstream s(input);
	s >> std::boolalpha >> t;
}

const JObject* JObject::GetObject(const char* name) const
{
	Map::const_iterator r = m_elements.find(std::string(name));

	if (r != m_elements.end())
		return r->second->GetObject();
	else
		return 0;
}

const JArray* JObject::GetArray(const char* name) const
{
	Map::const_iterator r = m_elements.find(std::string(name));

	if (r != m_elements.end())
		return r->second->GetArray();
	else
		return 0;
}

const JBase* JObject::Get(const char* name) const
{
	Map::const_iterator r = m_elements.find(std::string(name));

	if (r != m_elements.end())
		return r->second->GetArray();
	else
		return 0;
}

JObject* JObject::GetObject(const char* name)
{
	Map::const_iterator r = m_elements.find(std::string(name));

	if (r != m_elements.end())
		return r->second->GetObject();
	else
		return 0;
}

JArray* JObject::GetArray(const char* name)
{
	Map::const_iterator r = m_elements.find(std::string(name));

	if (r != m_elements.end())
		return r->second->GetArray();
	else
		return 0;
}

JBase* JObject::Get(const char* name)
{
	Map::const_iterator r = m_elements.find(std::string(name));

	if (r != m_elements.end())
		return r->second;
	else
		return 0;
}

void JObject::Insert(const std::string& name, std::string& value) 
{
	m_elements[name] = Ut::SharedPtr<JBase>(new JData(value));
}

void JObject::Insert(const std::string& name, JBase* value)
{
	Assert(value);
	m_elements[name] = Ut::MakeShared(value);
}

void JObject::Erase(const std::string& name)
{
	m_elements.erase(name);
}

JObject::Range JObject::GetMembers() const
{
	return Range(m_elements.begin(), m_elements.end());
}

void JObject::Erase(JBase* p)
{
	for (Map::iterator it = m_elements.begin(); it != m_elements.end(); ++it)
	{
		if (it->second == p)
		{
			m_elements.erase(it);
			return;
		}
	}
}

JBase* JObject::DeepCopy() const
{
	JObject* pNew = new JObject;

	for (Map::const_iterator it = m_elements.begin(); it != m_elements.end(); ++it)
		pNew->Insert(it->first, it->second->DeepCopy());

	return pNew;
}

/////////////////////////////////////////////////////

JObject* JArray::GetObject(int i)
{
	return m_elements[i]->GetObject();
}

JArray* JArray::GetArray(int i)
{
	return m_elements[i]->GetArray();
}

JBase* JArray::Get(int i)
{
	return m_elements[i];
}

const JObject* JArray::GetObject(int i) const
{
	return m_elements[i]->GetObject();
}

const JArray* JArray::GetArray(int i) const
{
	return m_elements[i]->GetArray();
}

const JBase* JArray::Get(int i) const
{
	return m_elements[i];
}

void JArray::PushBack(std::string& value) 
{
	m_elements.push_back(Ut::SharedPtr<JBase>(new JData(value)));
}

void JArray::PushBack(JBase* value) 
{
	Assert(value);
	m_elements.push_back(Ut::SharedPtr<JBase>(value));
}

void JArray::Set(int i, JBase* value)
{
	Assert(value);
	m_elements[i] = Ut::MakeShared(value);
}

void JArray::Erase(int i)
{
	m_elements.erase(m_elements.begin() + i);
}

void JArray::Erase(JBase* p)
{
	for (int i = 0; i < (int) m_elements.size(); i++)
	{
		if (m_elements[i] == p)
		{
			m_elements.erase(m_elements.begin() + i);
			return;
		}
	}
}

JBase* JArray::DeepCopy() const
{
	JArray* pNew = new JArray;

	for (int i = 0; i < (int) m_elements.size(); i++)
		pNew->PushBack(m_elements[i]->DeepCopy());

	return pNew;
}

////////////////////////////////////////////////////

JObject* ParseObject(std::ifstream& file, std::string& next);
JArray* ParseArray(std::ifstream& file, std::string& next);

JObject* Parse(std::ifstream& file)
{
	std::string s;
	std::getline(file, s);
	if (s != "{") 
		return 0;

	JObject* pObject = 0;

	if (!(pObject = ParseObject(file, s)))
		return 0;

	if (s != "}")
	{
		delete pObject;
		return 0;
	}

	return pObject;
}

std::string ReadLine(std::ifstream& file)
{
	std::string s;
	std::getline(file, s);
	std::string::size_type begin = s.find_first_not_of(" \t");
	std::string::size_type end = s.find_last_not_of(" \t");

	if (begin == std::string::npos || end == std::string::npos)
		return "";

	return s.substr(begin, end - begin + 1);
}

JObject* ParseObject(std::ifstream& file, std::string& next)
{
	JObject* pObject = new JObject;

	while (true)
	{
		std::string s = ReadLine(file);

		bool closeObject = s[0] == '}';

		if (!closeObject && s[0] != '\"')
		{
			delete pObject;
			return 0;
		}

		std::string name;
		ReadValue(s, name);

		bool openObject = s[s.size() - 1] == '{';
		bool openArray = s[s.size() - 1] == '[';

		if (openObject)
		{
			if (JObject* subObject = ParseObject(file, s))
			{
				pObject->Insert(name, subObject);
			}
			else
			{
				delete pObject;
				return 0;
			}
		}
		else if (openArray)
		{
			if (JArray* jArray = ParseArray(file, s))
			{
				pObject->Insert(name, jArray);
			}
			else
			{
				delete pObject;
				return 0;
			}
		}
		else if (closeObject)
		{
			next = s;
			return pObject;
		}
		else
		{
			std::string::size_type end = name.size() + 2;
			std::string::size_type comma = s.find(",", end + 2);
			std::string value;
			
			if (comma != std::string::npos)
				value = s.substr(end + 2, comma - end - 2);
			else
				value = s.substr(end + 2, s.size() - end + 2);

			pObject->Insert(name, value);
		}

		char last = s[s.size() - 1];
		if (last != ',')
			break;
	}

	next = ReadLine(file);
	return pObject;
}

JArray* ParseArray(std::ifstream& file, std::string& next)
{
	JArray* pArray = new JArray;

	while (true)
	{
		std::string s = ReadLine(file);

		bool openObject = s[s.size() - 1] == '{';
		bool openArray = s[s.size() - 1] == '[';
		bool closeArray = s[0] == ']';

		if (openObject)
		{
			if (JObject* subObject = ParseObject(file, s))
			{
				pArray->PushBack(subObject);
			}
			else
			{
				delete pArray;
				return 0;
			}
		}
		else if (openArray)
		{
			if (JArray* subArray = ParseArray(file, s))
			{
				pArray->PushBack(subArray);
			}
			else
			{
				delete pArray;
				return 0;
			}
		}
		else if (closeArray)
		{
			next = s;
			return pArray;
		}
		else
		{
			std::string::size_type comma = s.find(",");
			std::string value;
			
			if (comma != std::string::npos)
				value = s.substr(0, comma);
			else
				value = s.substr(0, s.size());

			pArray->PushBack(value);
		}

		char last = s[s.size() - 1];
		if (last != ',')
			break;
	}

	next = ReadLine(file);
	return pArray;
}

}